import './index.css'

import {
	DM_SCREEN, DM_TARGET,
	MODE_RTL, MODE_CENTER,
	DANMAKU_RTL, DANMAKU_CENTER
} from './constant'

class danmakuWorker {
	constructor() {
		this.group = []
		this.ani = []
		this.current = -1
		this.rate = 1
		this.screen = null
		this.zIndex = 10
		this.pool = []
		this.wait = false
		this.validatorFn = null
	}

	init() {
		this.current = -1
		this.pool = []
		this.group[0] = new Array(20).fill(0)
		this.group[1] = new Array(20).fill(0)
		this.wait = false
	}

	bind(parent = null) {
		if (this.screen && this.screen.classList.contains(DM_SCREEN)) {
			this.screen.classList.remove(DM_SCREEN)
		}
		this.screen = parent || document.body
		this.screen.classList.add(DM_SCREEN)
	}

	check(data) {
		const { text, time, mode, runway } = data
		return text && text.length > 0 && time >= 0 && this.getModeGroup(mode) >= 0 && runway >= 0
	}
	
	save(dom) {
		const data = this.parseDataFromDom(dom)
		if (this.check(data)) {
			const index = parseInt(data.time)
			if (!Array.isArray(this.pool[index])) {
				this.pool[index] = []
			}
			this.pool[index].push(dom)
			return true
		} else {
			return false
		}
	}

	getModeGroup(mode) {
		if ([MODE_RTL].includes(mode)) {
			return 0
		} else if ([MODE_CENTER].includes(mode)) {
			return 1
		} else {
			return -1
		}
	}

	setFontSize(val) {
		this.screen.style.fontSize = `${val}px`
	}

	setAniRate(val) {
		this.rate = val
		this.ani.forEach(item => item.playbackRate = val)
	}

	setDanmakuZIndex(zindex) {
		this.zIndex = zindex
	}

	setOpacity(val) {
		this.screen.style.opacity = val
	}

	setValidator(fn) {
		if (typeof fn === 'function') {
			this.validatorFn = fn
		}
	}

	parseHTMLFromString(str) {
		if (typeof str === 'string') {
			return [...(new DOMParser().parseFromString(str, 'text/html')).body.children].filter(item => item.hasAttribute('danmaku') && item.tagName === 'SPAN')
		}
	}

	parseDataFromDom(dom) {
		const data = dom.getAttribute('danmaku').split(' ')
		return {
			text: dom.textContent,
			time: data[0],
			mode: data[1],
			runway: data[2],
			color: data[3],
			user: data[4]
		}
	}

	serializaHTMLFromData(text, time, mode, runway, color, user) {
		return `<span class="${DM_TARGET}" danmaku="${time} ${mode} ${runway} ${color} ${user}">${text}</span>`
	}

	toGet({ text, mode, color = '#000000', time = 0, user = '*' } = {}) {
		if (!text || !text.length) return false
		let max = Number.MAX_SAFE_INTEGER, index = 0, arr = this.group[this.getModeGroup(mode)]
		if (arr) {
			for (let i = 0; i< arr.length; i++) {
				if (arr[i] < max) {
					index = i
					max = arr[i]
				}
			}
			console.log(index)
			const random = Math.floor(Math.random() * arr.length)
			const runway = Math.abs(index - random) > (arr.length / 2) - 1 ? index : random
			this.updateRunway(mode, runway, true)
			return this.serializaHTMLFromData(text, time, mode, runway, color, user)
		} else {
			return false
		}
	}

	toSave(html) {
		this.parseHTMLFromString(html).forEach(dom => {
			this.save(dom)
		})
	}

	toAdd(index, immediate = false, force = false) {
		index = parseInt(index)
		if (index === this.current && !force) return
		this.current = index
		if (Array.isArray(this.pool[index])) {
			this.pool[index].forEach(dom => {
				if (this.toValidator(dom)) {
					this.toSend(dom, immediate)
				}
			})
		}
	}

	toLaunch(str) {
		const arr = this.parseHTMLFromString(str)
		if (arr && arr[0]) {
			const dom = arr[0]
			if (this.check(this.parseDataFromDom(dom)) && this.save(dom) && this.toValidator(dom)) {
				this.toSend(dom, true)
			}
		}
	}

	toValidator(dom) {
		if (this.validatorFn) {
			const { text, color, user, mode } = this.parseDataFromDom(dom)
			const pass = this.validatorFn(text, color, user, mode)
			if (typeof pass === 'boolean') {
				return pass
			} else {
				return true
			}
		} else {
			return true
		}
	}

	toSend(dom, immediate) {
		const { text, time, mode, runway, color, user } = this.parseDataFromDom(dom)
		setTimeout(() => {
			this.screen.appendChild(dom)
			const { duration, options } = this.createDM(dom, mode, runway, color)
			new Promise((resolve, reject) => {
				const animation = dom.animate(options, { duration })
				if (this.wait) animation.pause()
				animation.playbackRate = this.rate
				animation.onfinish = () => {
					dom.remove()
				}
				resolve(animation)
			}).then(animation => {
				this.ani.push(animation)
				this.updateRunway(mode, runway, false)
			})
		}, immediate ? 0 : (time % 1) * 1000)
	}

	toToggle() {
		this.screen.classList.toggle('hide')
	}

	toPlay() {
		this.wait = false
		this.ani.forEach(item => item.play())
	}

	toPause() {
		this.wait = true
		this.ani.forEach(item => item.pause())
	}

	toClean() {
		this.ani.forEach(item => item.finish())
	}
	
	createDM(dom, mode, runway, color) {
		dom.classList.add(DM_TARGET)		dom.style.color = color
		dom.style.top = `${(runway * 100) / 20}%`
		dom.dataset.mode = mode
		switch(mode) {
			case MODE_CENTER:
				dom.style.zIndex = this.zIndex + DANMAKU_CENTER
				return this.createDMOfCenter(dom, runway)
			case MODE_RTL:
			default:
				dom.style.zIndex = this.zIndex + DANMAKU_RTL
				return this.createDMOfRtl(dom, runway)
		}
	}
	
	createDMOfRtl(dom, runway) {
		const duration = 16000
		const options = [
			{ left: '100%' },
			{ left: `-${this.screen.offsetWidth + dom.offsetWidth}px` }
		]
		return { duration, options }
	}
	
	createDMOfCenter(dom, runway) {
		const duration = 8000
		const options = []
		return { duration, options }
	}
	
	updateRunway(mode, runway, long) {
		const arr = this.group[this.getModeGroup(mode)]
		arr[runway]++
		setTimeout(() => {
			arr[runway]--
		}, long ? 16000 : 4000)
	}
}

export default danmakuWorker